<?php
/**
 * @ClassName: Api
 * @Description: API控制器基类--token认证
 * @author: gydtrade
 * @Date: 2024-09-08 13:30
 * @Version: V1.0.0
 */

namespace app\common\controller;

use app\common\model;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use think\App;
use think\Container;
use think\exception\HttpResponseException;
use think\facade\Cache;
use think\Response;
use think\Validate;

class Api
{
    /**
     * Token 加密 Key
     * www.gydtrade.com Base64编码
     * @var string
     */
    protected $key = 'ZDNkM0xtZDVaSFJ5WVdSbExtTnZiUT09';
    /**
     * 会员ID
     * @var int
     */
    protected $member_id = 0;
    /**
     * 会员
     * @var null
     */
    protected $member;
    /**
     * 无需登录的方法
     * @var array
     */
    protected $noNeedLogin = [];
    /**
     * 登录失效时间，默认超过10天后需要重新认证,只要在有效期内登录过app就会自动续期
     * @var int
     */
    protected $invalidationTime = 864000;
    /**
     * 默认响应输出类型,支持json/xml
     * @var string
     */
    protected $responseType = 'json';
    /**
     * Request实例
     * @var \think\Request
     */
    protected $request;
    /**
     * 应用实例
     * @var \think\App
     */
    protected $app;
    /**
     * Token
     * @var null
     */
    protected $token = null;

    /**
     * 构造方法
     * @param App|null $app
     */
    public function __construct(App $app = null)
    {
        // 控制器初始化
        $this->initialize();

        $this->app = $app ?: Container::get('app');
        $this->request = $this->app['request'];
        $token = $this->request->header('Authorization', '');
        if ($token) {
            $this->token = $token;
        }

        foreach ($this->noNeedLogin as &$item) {
            $item = strtolower($item);
        }
        unset($item);

        $this->verifyLoginStatus();
    }

    /**
     * 初始化方法
     * @return void
     */
    protected function initialize()
    {
        header("Access-Control-Allow-Origin: *");
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Max-Age: 86400');
        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Form-Type, Accept, Access-Token, device-id, verify-encrypt, verify-time');
        if (request()->isOptions()) {
            exit();
        }
    }

    /**
     * 返回成功信息
     * @param $data
     * @param $msg
     * @param $token
     * @return void
     */
    protected function success($data = [], $msg = 'ok', $token = false)
    {
        if ($token) {
            $encodeToken = $this->encode();
            $this->token = $encodeToken;
            $payload = [
                'member_id' => $this->member_id,
                'login_time' => date_now(),
                'device_info' => request()->server('http_user_agent')
            ];

            Cache::set('online:' . $encodeToken, $payload, $this->invalidationTime);  // 864000 等于10天
            $this->result($data, 1, $msg, 'json', $encodeToken);
        } else {
            $this->result($data, 1, $msg, 'json');
        }
    }

    /**
     * 返回错误信息
     * @param $msg
     * @param $data
     * @param $statusCode
     * @return void
     */
    protected function error($msg, $data = [], $statusCode = 200)
    {
        $this->result($data, 0, $msg, 'json', '', $statusCode);
    }

    private function result($data, int $code = 0, $msg = '', string $type = '', $token = '', $status = 200)
    {
        $result = [
            'code' => $code,
            'msg' => $msg,
            'time' => time(),
            'data' => $data,
            'token' => $token,
        ];

        /*if (!isset($result['data']) || empty($data)) {
            unset($result['data']);
        }*/
        if (!isset($result['token']) || empty($token)) {
            unset($result['token']);
        }

        $type = $type ?: $this->responseType;
        $response = Response::create($result, $type)->code($status);
        throw new HttpResponseException($response);
    }

    /**
     * 验证登录状态
     * @return void
     */
    private function verifyLoginStatus()
    {
        // 不需要登录的请求方法，如果请求方法不存在的话默认输出无权访问
        $noNeedLogin = (in_array($this->request->action(), $this->noNeedLogin) || in_array('*', $this->noNeedLogin));
        if (!$noNeedLogin) {
            if (empty($this->token)) {
                $this->result('', 0, '未登录,无权访问', 'json', [], 403);
            }
            $this->verifyToken();
        }
        // 方法已经添加到noNeedLogin。但是请求带Token也会获取用户ID信息
        if ($this->token) {
            $this->verifyToken();
        }
    }

    /**
     * 验证Token
     */
    private function verifyToken()
    {
        $this->decode($this->token);
        $result = Cache::get('online:' . $this->token);

        if (!$result) {
            $this->result('', 0, '登录失效，请重新登录-2', 'json', [], 401);
        }

        $member = model\Member::where(['id' => $result['member_id']])->find();
        if (!$member) {
            $this->result('', 0, '账号不存在' . $result['member_id'] . json_encode($result), 'json', [], 401);
        } else {
            if ($member['status'] == 0) {
                $this->result('', 0, '账户状态异常', 'json', [], 401);
            }
        }

        $this->member_id = $result['member_id'];
        // 续期
        $payload = [
            'member_id' => $this->member_id,
            'login_time' => date_now(),
            'device_info' => request()->server('http_user_agent')
        ];

        Cache::set('online:' . $this->token, $payload, $this->invalidationTime);  // 864000 等于10天
    }

    /**
     * 解密
     * @param $token
     * @return array|void
     */
    protected function decode($token)
    {
        try {
            $decode = JWT::decode($token, new Key($this->key, 'HS256'));
            return (array)$decode;
        } catch (\Exception $exception) {
            $this->result('', 0, '登录失效，请重新登录-1', 'json', [], 401);
        }
    }

    /**
     * 加密
     * @return mixed
     */
    protected function encode()
    {
        $payload = [
            'iss' => 'gydtrade', // 签发组织
            'aud' => '隔云端', // 签发作者
            'iat' => time(), // 签发时间
            "nbf" => time(), // 生效时间
            "exp" => time() + $this->invalidationTime, // token 过期时间 10天(864000,3600为一小时)
            'jti' => uniqid('GYD'), // uuid
            'member_id' => 'gyd_' . $this->member_id, //记录的memberid的信息，这里是自已添加上去的，如果有其它信息，可以再添加数组的键值对
            'version' => '1.0.0',
        ];
        //生成Token
        $encodeToken = JWT::encode($payload, $this->key, 'HS256');

        return $encodeToken;
    }

    /**
     * 验证数据
     * @access protected
     *
     * @param array $data 数据
     * @param string|array $validate 验证器名或者验证规则数组
     * @param array $message 提示信息
     * @param bool $batch 是否批量验证
     * @param mixed $callback 回调方法（闭包）
     *
     * @return array|string|true
     */
    protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
    {
        $v = new Validate();
        $v->rule($validate);
        // 是否批量验证
        if ($batch) {
            $v->batch(true);
        }
        if (is_array($message)) {
            $v->message($message);
        }
        if ($callback && is_callable($callback)) {
            call_user_func_array($callback, [$v, &$data]);
        }
        if (!$v->check($data)) {
            return $v->getError();
        }
        return true;
    }

}